/*===================================================================*/
/*                                                                   */
/*  InfoNES.cpp : NES Emulator for Win32, Linux(x86), Linux(PS2)     */
/*                                                                   */
/*  2000/05/18  InfoNES Project ( based on pNesX )                   */
/*                                                                   */
/*===================================================================*/

/*-------------------------------------------------------------------
* File List :
*
* [NES Hardware]
*   InfoNES.cpp
*   InfoNES.h
*   K6502_rw.h
*
* [Mapper function]
*   InfoNES_Mapper.cpp
*   InfoNES_Mapper.h
*
* [The function which depends on a system]
*   InfoNES_System_ooo.cpp (ooo is a system name. win, ...)
*   InfoNES_System.h
*
* [CPU]
*   K6502.cpp
*   K6502.h
*
* [Others]
*   InfoNES_Types.h
*
--------------------------------------------------------------------*/

/*-------------------------------------------------------------------*/
/*  Include files                                                    */
/*-------------------------------------------------------------------*/


#include "InfoNES.h"
#include "InfoNES_System.h"
#include "InfoNES_Mapper.h"
//#include "InfoNES_pAPU.h"
#include "K6502.h"

/*-------------------------------------------------------------------*/
/*  NES resources                                                    */
/*-------------------------------------------------------------------*/

/* RAM */
u8 RAM[ RAM_SIZE ];

/* SRAM */
u8 SRAM[ SRAM_SIZE ];

/* ROM */
u8 *ROM;

/* SRAM BANK ( 8Kb ) */
u8 *SRAMBANK;

/* ROM BANK ( 8Kb * 4 ) */
u8 *ROMBANK0;
u8 *ROMBANK1;
u8 *ROMBANK2;
u8 *ROMBANK3;

/*-------------------------------------------------------------------*/
/*  PPU resources                                                    */
/*-------------------------------------------------------------------*/

/* PPU RAM */
u8 PPURAM[ PPURAM_SIZE ];

/* VROM */
u8 *VROM;

/* PPU BANK ( 1Kb * 16 ) */
u8 *PPUBANK[ 16 ];

/* Sprite RAM */
u8 SPRRAM[ SPRRAM_SIZE ];

/* PPU Register */
u8 PPU_R0;
u8 PPU_R1;
u8 PPU_R2;
u8 PPU_R3;
u8 PPU_R7;

/* Vertical scroll value */
u8 PPU_Scr_V;
u8 PPU_Scr_V_Next;
u8 PPU_Scr_V_u8;
u8 PPU_Scr_V_u8_Next;
u8 PPU_Scr_V_Bit;
u8 PPU_Scr_V_Bit_Next;

/* Horizontal scroll value */
u8 PPU_Scr_H;
u8 PPU_Scr_H_Next;
u8 PPU_Scr_H_u8;
u8 PPU_Scr_H_u8_Next;
u8 PPU_Scr_H_Bit;
u8 PPU_Scr_H_Bit_Next;

/* PPU Address */
u16 PPU_Addr;

/* PPU Address */
u16 PPU_Temp;

/* The increase value of the PPU Address */
u16 PPU_Increment;

/* Current Scanline */
u16 PPU_Scanline;

/* Scanline Table */
u8 PPU_ScanTable[ 263 ];

/* Name Table Bank */
u8 PPU_NameTableBank;

/* BG Base Address */
u8 *PPU_BG_Base;

/* Sprite Base Address */
u8 *PPU_SP_Base;

/* Sprite Height */
u16 PPU_SP_Height;

/* Sprite #0 Scanline Hit Position */
int SpriteJustHit;

/* VRAM Write Enable ( 0: Disable, 1: Enable ) */
u8 byVramWriteEnable;

/* PPU Address and Scroll Latch Flag*/
u8 PPU_Latch_Flag;

/* Up and Down Clipping Flag ( 0: non-clip, 1: clip ) */ 
u8 PPU_UpDown_Clip;

/* Frame IRQ ( 0: Disabled, 1: Enabled )*/
u8 FrameIRQ_Enable;
u16 FrameStep;

/*-------------------------------------------------------------------*/
/*  Display and Others resouces                                      */
/*-------------------------------------------------------------------*/

/* Frame Skip */
u16 FrameSkip;
u16 FrameCnt;

/* Display Buffer */
u16 DoubleFrame[ 2 ][ NES_DISP_WIDTH * NES_DISP_HEIGHT ];
u16 *WorkFrame;
u16 WorkFrameIdx;

/* Character Buffer */
u8 ChrBuf[ 256 * 2 * 8 * 8 ];

/* Update flag for ChrBuf */
u8 ChrBufUpdate;

/* Palette Table */
u16 PalTable[ 32 ];

/* Table for Mirroring */
u8 PPU_MirrorTable[][ 4 ] =
{
	{ NAME_TABLE0, NAME_TABLE0, NAME_TABLE1, NAME_TABLE1 },
	{ NAME_TABLE0, NAME_TABLE1, NAME_TABLE0, NAME_TABLE1 },
	{ NAME_TABLE1, NAME_TABLE1, NAME_TABLE1, NAME_TABLE1 },
	{ NAME_TABLE0, NAME_TABLE0, NAME_TABLE0, NAME_TABLE0 }
};

/*-------------------------------------------------------------------*/
/*  APU and Pad resources                                            */
/*-------------------------------------------------------------------*/

/* APU Register */
u8 APU_Reg[ 0x18 ];

/* APU Mute ( 0:OFF, 1:ON ) */
int APU_Mute = 0;

/* Pad data */
u32 PAD1_Latch;
u32 PAD2_Latch;
u32 PAD_System;
u32 PAD1_Bit;
u32 PAD2_Bit;

/*-------------------------------------------------------------------*/
/*  Mapper Function                                                  */
/*-------------------------------------------------------------------*/

/* Initialize Mapper */
void (*MapperInit)();
/* Write to Mapper */
void (*MapperWrite)( u16 wAddr, u8 byData );
/* Write to SRAM */
void (*MapperSram)( u16 wAddr, u8 byData );
/* Write to Apu */
void (*MapperApu)( u16 wAddr, u8 byData );
/* Read from Apu */
u8 (*MapperReadApu)( u16 wAddr );
/* Callback at VSync */
void (*MapperVSync)();
/* Callback at HSync */
void (*MapperHSync)();
/* Callback at PPU read/write */
void (*MapperPPU)( u16 wAddr );
/* Callback at Rendering Screen 1:BG, 0:Sprite */
void (*MapperRenderScreen)( u8 byMode );

/*-------------------------------------------------------------------*/
/*  ROM information                                                  */
/*-------------------------------------------------------------------*/

/* .nes File Header */
struct NesHeader_tag NesHeader;

/* Mapper Number */
u8 MapperNo;

/* Mirroring 0:Horizontal 1:Vertical */
u8 ROM_Mirroring;
/* It has SRAM */
u8 ROM_SRAM;
/* It has Trainer */
u8 ROM_Trainer;
/* Four screen VRAM  */
u8 ROM_FourScr;




/*===================================================================*/
/*                                                                   */
/*                InfoNES_Init() : Initialize InfoNES                */
/*                                                                   */
/*===================================================================*/
void InfoNES_Init()
{
/*
*  Initialize InfoNES
*
*  Remarks
*    Initialize K6502 and Scanline Table.
	*/
	int nIdx;
	
	// Initialize 6502
	K6502_Init();
	
	// Initialize Scanline Table
	for ( nIdx = 0; nIdx < 263; ++nIdx )
	{
		if ( nIdx < SCAN_ON_SCREEN_START )
			PPU_ScanTable[ nIdx ] = SCAN_ON_SCREEN;
		else
			if ( nIdx < SCAN_BOTTOM_OFF_SCREEN_START )
				PPU_ScanTable[ nIdx ] = SCAN_ON_SCREEN;
			else
				if ( nIdx < SCAN_UNKNOWN_START )
					PPU_ScanTable[ nIdx ] = SCAN_ON_SCREEN;
				else
					if ( nIdx < SCAN_VBLANK_START )
						PPU_ScanTable[ nIdx ] = SCAN_UNKNOWN;
					else
						PPU_ScanTable[ nIdx ] = SCAN_VBLANK;
	}
}

/*===================================================================*/
/*                                                                   */
/*                InfoNES_Fin() : Completion treatment               */
/*                                                                   */
/*===================================================================*/
void InfoNES_Fin()
{
/*
*  Completion treatment
*
*  Remarks
*    Release resources
	*/
	// Finalize pAPU
	//InfoNES_pAPUDone();
	
	// Release a memory for ROM
	InfoNES_ReleaseRom();
}

/*===================================================================*/
/*                                                                   */
/*                  InfoNES_Load() : Load a cassette                 */
/*                                                                   */
/*===================================================================*/
int InfoNES_Load( const char *pszFileName )
{
/*
*  Load a cassette
*
*  Parameters
*    const char *pszFileName            (Read)
*      File name of ROM image
*
*  Return values
*     0 : It was finished normally.
*    -1 : An error occurred.
*
*  Remarks
*    Read a ROM image in the memory. 
*    Reset InfoNES.
	*/
	
	// Release a memory for ROM
	InfoNES_ReleaseRom();
	
	// Read a ROM image in the memory
	if ( InfoNES_ReadRom( pszFileName ) < 0 )
		return -1;
	
	// Reset InfoNES
	if ( InfoNES_Reset() < 0 )
		return -1;
	
	// Successful
	return 0;
}

/*===================================================================*/
/*                                                                   */
/*                 InfoNES_Reset() : Reset InfoNES                   */
/*                                                                   */
/*===================================================================*/
int InfoNES_Reset()
{
/*
*  Reset InfoNES
*
*  Return values
*     0 : Normally
*    -1 : Non support mapper
*
*  Remarks
*    Initialize Resources, PPU and Mapper.
*    Reset CPU.
	*/
	
	int nIdx;
	
	/*-------------------------------------------------------------------*/
	/*  Get information on the cassette                                  */
	/*-------------------------------------------------------------------*/
	
	// Get Mapper Number
	MapperNo = NesHeader.byInfo1 >> 4;
	
	// Check bit counts of Mapper No.
	for ( nIdx = 4; nIdx < 8 && NesHeader.byReserve[ nIdx ] == 0; ++nIdx )
		;
	
	if ( nIdx == 8 )
	{
		// Mapper Number is 8bits
		MapperNo |= ( NesHeader.byInfo2 & 0xf0 );
	}
	
	// Get information on the ROM
	ROM_Mirroring = NesHeader.byInfo1 & 1;
	ROM_SRAM 	= NesHeader.byInfo1 & 2;
	ROM_Trainer = NesHeader.byInfo1 & 4;
	ROM_FourScr = NesHeader.byInfo1 & 8;
	
	/*-------------------------------------------------------------------*/
	/*  Initialize resources                                             */
	/*-------------------------------------------------------------------*/
	
	// Clear RAM
	InfoNES_MemorySet( RAM, 0, sizeof RAM );
	
	// Reset frame skip and frame count
	FrameSkip = 0;
	FrameCnt = 0;
	
	// Reset work frame
	WorkFrame = DoubleFrame[ 0 ];
	WorkFrameIdx = 0;
	
	// Reset update flag of ChrBuf
	ChrBufUpdate = 0xff;
	
	// Reset palette table
	InfoNES_MemorySet( PalTable, 0, sizeof PalTable );
	
	// Reset APU register
	InfoNES_MemorySet( APU_Reg, 0, sizeof APU_Reg );
	
	// Reset joypad
	PAD1_Latch = PAD2_Latch = PAD_System = 0;
	PAD1_Bit = PAD2_Bit = 0;
	
	/*-------------------------------------------------------------------*/
	/*  Initialize PPU                                                   */
	/*-------------------------------------------------------------------*/
	
	InfoNES_SetupPPU();
	
	/*-------------------------------------------------------------------*/
	/*  Initialize pAPU                                                  */
	/*-------------------------------------------------------------------*/
	
	//InfoNES_pAPUInit();
	
	/*-------------------------------------------------------------------*/
	/*  Initialize Mapper                                                */
	/*-------------------------------------------------------------------*/
	
	// Get Mapper Table Index
	for ( nIdx = 0; MapperTable[ nIdx ].nMapperNo != -1; ++nIdx )
	{
		if ( MapperTable[ nIdx ].nMapperNo == MapperNo )
			break;
	}
	
	if ( MapperTable[ nIdx ].nMapperNo == -1 )
	{
		// Non support mapper
		return -1;
	}
	
	// Set up a mapper initialization function
	MapperTable[ nIdx ].pMapperInit();
	
	/*-------------------------------------------------------------------*/
	/*  Reset CPU                                                        */
	/*-------------------------------------------------------------------*/
	
	K6502_Reset();
	
	// Successful
	return 0;
}

/*===================================================================*/
/*                                                                   */
/*                InfoNES_SetupPPU() : Initialize PPU                */
/*                                                                   */
/*===================================================================*/
void InfoNES_SetupPPU()
{
/*
*  Initialize PPU
*
	*/
	int nPage;
	
	// Clear PPU and Sprite Memory
	InfoNES_MemorySet( PPURAM, 0, sizeof PPURAM );
	InfoNES_MemorySet( SPRRAM, 0, sizeof SPRRAM );
	
	// Reset PPU Register
	PPU_R0 = PPU_R1 = PPU_R2 = PPU_R3 = PPU_R7 = 0;
	
	// Reset latch flag
	PPU_Latch_Flag = 0;
	
	// Reset up and down clipping flag
	PPU_UpDown_Clip = 1;
	
	FrameStep = 0;
	FrameIRQ_Enable = 0;
	
	// Reset Scroll values
	PPU_Scr_V = PPU_Scr_V_Next = PPU_Scr_V_u8 = PPU_Scr_V_u8_Next = PPU_Scr_V_Bit = PPU_Scr_V_Bit_Next = 0;
	PPU_Scr_H = PPU_Scr_H_Next = PPU_Scr_H_u8 = PPU_Scr_H_u8_Next = PPU_Scr_H_Bit = PPU_Scr_H_Bit_Next = 0;
	
	// Reset PPU address
	PPU_Addr = 0;
	PPU_Temp = 0;
	
	// Reset scanline
	PPU_Scanline = 0;
	
	// Reset hit position of sprite #0 
	SpriteJustHit = 0;
	
	// Reset information on PPU_R0
	PPU_Increment = 1;
	PPU_NameTableBank = NAME_TABLE0;
	PPU_BG_Base = ChrBuf;
	PPU_SP_Base = ChrBuf + 256 * 64;
	PPU_SP_Height = 8;
	
	// Reset PPU banks
	for ( nPage = 0; nPage < 16; ++nPage )
		PPUBANK[ nPage ] = &PPURAM[ nPage * 0x400 ];
	
	/* Mirroring of Name Table */
	InfoNES_Mirroring( ROM_Mirroring );
	
	/* Reset VRAM Write Enable */
	byVramWriteEnable = ( NesHeader.byVRomSize == 0 ) ? 1 : 0;
}

/*===================================================================*/
/*                                                                   */
/*       InfoNES_Mirroring() : Set up a Mirroring of Name Table      */
/*                                                                   */
/*===================================================================*/
void InfoNES_Mirroring( int nType )
{
/*
*  Set up a Mirroring of Name Table
*
*  Parameters
*    int nType          (Read)
*      Mirroring Type
*        0 : Horizontal
*        1 : Vertical
*        2 : One Screen 0x2400
*        3 : One Screen 0x2000
	*/
	
	PPUBANK[ NAME_TABLE0 ] = &PPURAM[ PPU_MirrorTable[ nType ][ 0 ] * 0x400 ];
	PPUBANK[ NAME_TABLE1 ] = &PPURAM[ PPU_MirrorTable[ nType ][ 1 ] * 0x400 ];
	PPUBANK[ NAME_TABLE2 ] = &PPURAM[ PPU_MirrorTable[ nType ][ 2 ] * 0x400 ];
	PPUBANK[ NAME_TABLE3 ] = &PPURAM[ PPU_MirrorTable[ nType ][ 3 ] * 0x400 ];
}

/*===================================================================*/
/*                                                                   */
/*              InfoNES_Main() : The main loop of InfoNES            */
/*                                                                   */
/*===================================================================*/
void InfoNES_Main()
{
/*
*  The main loop of InfoNES
*
	*/
	
	// Initialize InfoNES
	InfoNES_Init();
	
	DebugPrintf("NES Startup\n");
	// Main loop
	while ( 1 )
	{
		/*-------------------------------------------------------------------*/
		/*  To the menu screen                                               */
		/*-------------------------------------------------------------------*/
		if ( InfoNES_Menu() == -1 )
			break;  // Quit
		
		/*-------------------------------------------------------------------*/
		/*  Start a NES emulation                                            */
		/*-------------------------------------------------------------------*/
		InfoNES_Cycle();
	}
	
	// Completion treatment
	InfoNES_Fin();
}

/*===================================================================*/
/*                                                                   */
/*              InfoNES_Cycle() : The loop of emulation              */
/*                                                                   */
/*===================================================================*/
void InfoNES_Cycle()
{
/*
*  The loop of emulation
*
	*/
	
	// Set the PPU adress to the buffered value
	if ( ( PPU_R1 & R1_SHOW_SP ) || ( PPU_R1 & R1_SHOW_SCR ) )
		PPU_Addr = PPU_Temp;
	
	// Emulation loop
	for (;;)
	{    
		int nStep;
		
		// Set a flag if a scanning line is a hit in the sprite #0
		if ( SpriteJustHit == PPU_Scanline &&
			PPU_ScanTable[ PPU_Scanline ] == SCAN_ON_SCREEN )
		{
			// # of Steps to execute before sprite #0 hit
			nStep = SPRRAM[ SPR_X ] * STEP_PER_SCANLINE / NES_DISP_WIDTH;
			
			// Execute instructions
			K6502_Step( nStep );
			
			// Set a sprite hit flag
			PPU_R2 |= R2_HIT_SP;
			
			// NMI is required if there is necessity
			if ( ( PPU_R0 & R0_NMI_SP ) && ( PPU_R1 & R1_SHOW_SP ) )
				NMI_REQ;
			
			// Execute instructions
			K6502_Step( STEP_PER_SCANLINE - nStep );
		}
		else
		{
			// Execute instructions
			K6502_Step( STEP_PER_SCANLINE );
		}
		
		// Frame IRQ in H-Sync
		FrameStep += STEP_PER_SCANLINE;
		if ( FrameStep > STEP_PER_FRAME && FrameIRQ_Enable )
		{
			FrameStep %= STEP_PER_FRAME;
			IRQ_REQ;
			APU_Reg[ 0x4015 ] |= 0x40;
		}
		
		// A mapper function in H-Sync
		MapperHSync();
		
		// A function in H-Sync
		if ( InfoNES_HSync() == -1 )
			return;  // To the menu screen
		
		// HSYNC Wait
		InfoNES_Wait();
	}
}

/*===================================================================*/
/*                                                                   */
/*              InfoNES_HSync() : A function in H-Sync               */
/*                                                                   */
/*===================================================================*/
int InfoNES_HSync()
{
/*
*  A function in H-Sync
*
*  Return values
*    0 : Normally
*   -1 : Exit an emulation
	*/
	
	/*-------------------------------------------------------------------*/
	/*  Render a scanline                                                */
	/*-------------------------------------------------------------------*/
	if ( FrameCnt == 0 &&
		PPU_ScanTable[ PPU_Scanline ] == SCAN_ON_SCREEN )
	{
		InfoNES_DrawLine();
	}
	
	/*-------------------------------------------------------------------*/
	/*  Set new scroll values                                            */
	/*-------------------------------------------------------------------*/
	PPU_Scr_V      = PPU_Scr_V_Next;
	PPU_Scr_V_u8 = PPU_Scr_V_u8_Next;
	PPU_Scr_V_Bit  = PPU_Scr_V_Bit_Next;
	
	PPU_Scr_H      = PPU_Scr_H_Next;
	PPU_Scr_H_u8 = PPU_Scr_H_u8_Next;
	PPU_Scr_H_Bit  = PPU_Scr_H_Bit_Next;
	
	/*-------------------------------------------------------------------*/
	/*  Next Scanline                                                    */
	/*-------------------------------------------------------------------*/
	PPU_Scanline = ( PPU_Scanline == SCAN_VBLANK_END ) ? 0 : PPU_Scanline + 1;
	
	/*-------------------------------------------------------------------*/
	/*  Operation in the specific scanning line                          */
	/*-------------------------------------------------------------------*/
	switch ( PPU_Scanline )
	{
    case SCAN_TOP_OFF_SCREEN:
		// Reset a PPU status
		PPU_R2 = 0;
		
		// Set up a character data
		if ( NesHeader.byVRomSize == 0 && FrameCnt == 0 )
			InfoNES_SetupChr();
		
		// Get position of sprite #0
		InfoNES_GetSprHitY();
		break;
		
    case SCAN_UNKNOWN_START:
		if ( FrameCnt == 0 )
		{
			// Transfer the contents of work frame on the screen
			InfoNES_LoadFrame();
			
			// Switching of the double buffer
			if(WorkFrameIdx==0)
			{
				WorkFrameIdx=1;
			}
			else
			{
				WorkFrameIdx=0;
			}
			
			//WorkFrameIdx = 1 - WorkFrameIdx;
			WorkFrame = DoubleFrame[ WorkFrameIdx ];
			//DebugPrintf("@ %08XH\n",WorkFrame);
		}
		break;
		
    case SCAN_VBLANK_START:
		// FrameCnt + 1
		
		FrameCnt = ( FrameCnt >= FrameSkip ) ? 0 : FrameCnt + 1;
		
		
		// Set a V-Blank flag
		PPU_R2 = R2_IN_VBLANK;
		
		// Reset latch flag
		PPU_Latch_Flag = 0;
		
		// pAPU Sound function in V-Sync
		if ( !APU_Mute )
			//InfoNES_pAPUVsync();
		
		// A mapper function in V-Sync
		MapperVSync();
		
		// Get the condition of the joypad
		InfoNES_PadState( &PAD1_Latch, &PAD2_Latch, &PAD_System );
		
		// NMI on V-Blank
		if ( PPU_R0 & R0_NMI_VB )
			NMI_REQ;
		
		// Exit an emulation if a QUIT button is pushed
		if ( PAD_PUSH( PAD_System, PAD_SYS_QUIT ) )
			return -1;  // Exit an emulation      
		
		break;
	}
	
	// Successful
	return 0;
}

/*===================================================================*/
/*                                                                   */
/*              InfoNES_DrawLine() : Render a scanline               */
/*                                                                   */
/*===================================================================*/
void InfoNES_DrawLine()
{
/*
*  Render a scanline
*
	*/
	
	int nX;
	int nY;
	int nY4;
	int nYBit;
	u16 *pPalTbl;
	u8 *pAttrBase;
	u16 *pPoint;
	int nNameTable;
	u8 *pbyNameTable;
	u8 *pbyChrData;
	u8 *pSPRRAM;
	int nAttr;
	int nSprCnt;
	int nIdx;
	int nSprData;
	u8 bySprCol;
	u8 pSprBuf[ NES_DISP_WIDTH + 7 ];
	
	/*-------------------------------------------------------------------*/
	/*  Render Background                                                */
	/*-------------------------------------------------------------------*/
	
	/* MMC5 VROM switch */
	MapperRenderScreen( 1 );
	
	// Pointer to the render position
	pPoint = &WorkFrame[ PPU_Scanline * NES_DISP_WIDTH ];
	
	// Clear a scanline if up and down clipping flag is set
	if ( PPU_UpDown_Clip &&
		( SCAN_ON_SCREEN_START > PPU_Scanline 
		|| PPU_Scanline > SCAN_BOTTOM_OFF_SCREEN_START ) )
	{
		InfoNES_MemorySet( pPoint, 0, NES_DISP_WIDTH << 1 );
		return;
	}
	
	// Clear a scanline if screen is off
	if ( !( PPU_R1 & R1_SHOW_SCR ) )
	{
		InfoNES_MemorySet( pPoint, 0, NES_DISP_WIDTH << 1 );
		return;
	}
	
	nNameTable = PPU_NameTableBank;
	
	nY = PPU_Scr_V_u8 + ( PPU_Scanline >> 3 );
	
	nYBit = PPU_Scr_V_Bit + ( PPU_Scanline & 7 );
	
	if ( nYBit > 7 )
	{
		++nY;
		nYBit &= 7;
	}
	nYBit <<= 3;
	
	if ( nY > 29 )
	{
		// Next NameTable (An up-down direction)
		nNameTable ^= NAME_TABLE_V_MASK;
		nY -= 30;
	}
	
	nX = PPU_Scr_H_u8;
	
	nY4 = ( ( nY & 2 ) << 1 );
	
	/*-------------------------------------------------------------------*/
	/*  Rendering of the block of the left end                           */
	/*-------------------------------------------------------------------*/
	
	pbyNameTable = PPUBANK[ nNameTable ] + nY * 32 + nX;
	pbyChrData = PPU_BG_Base + ( *pbyNameTable << 6 ) + nYBit;
	pAttrBase = PPUBANK[ nNameTable ] + 0x3c0 + ( nY / 4 ) * 8;
	pPalTbl =  &PalTable[ ( ( ( pAttrBase[ nX >> 2 ] >> ( ( nX & 2 ) + nY4 ) ) & 3 ) << 2 ) ];
	
	for ( nIdx = PPU_Scr_H_Bit; nIdx < 8; ++nIdx )
	{
		*( pPoint++ ) = pPalTbl[ pbyChrData[ nIdx ] ];
	}
	
	// Callback at PPU read/write
	MapperPPU( PATTBL( pbyChrData ) );
	
	++nX;
	++pbyNameTable;
	
	/*-------------------------------------------------------------------*/
	/*  Rendering of the left table                                      */
	/*-------------------------------------------------------------------*/
	
	for ( ; nX < 32; ++nX )
	{
		pbyChrData = PPU_BG_Base + ( *pbyNameTable << 6 ) + nYBit;
		pPalTbl = &PalTable[ ( ( ( pAttrBase[ nX >> 2 ] >> ( ( nX & 2 ) + nY4 ) ) & 3 ) << 2 ) ];
		
		pPoint[ 0 ] = pPalTbl[ pbyChrData[ 0 ] ]; 
		pPoint[ 1 ] = pPalTbl[ pbyChrData[ 1 ] ];
		pPoint[ 2 ] = pPalTbl[ pbyChrData[ 2 ] ];
		pPoint[ 3 ] = pPalTbl[ pbyChrData[ 3 ] ];
		pPoint[ 4 ] = pPalTbl[ pbyChrData[ 4 ] ];
		pPoint[ 5 ] = pPalTbl[ pbyChrData[ 5 ] ];
		pPoint[ 6 ] = pPalTbl[ pbyChrData[ 6 ] ];
		pPoint[ 7 ] = pPalTbl[ pbyChrData[ 7 ] ];
		pPoint += 8;
		
		// Callback at PPU read/write
		MapperPPU( PATTBL( pbyChrData ) );
		
		++pbyNameTable;
	}
	
	// Holizontal Mirror
	nNameTable ^= NAME_TABLE_H_MASK;
	
	pbyNameTable = PPUBANK[ nNameTable ] + nY * 32;
	pAttrBase = PPUBANK[ nNameTable ] + 0x3c0 + ( nY / 4 ) * 8;
	
	/*-------------------------------------------------------------------*/
	/*  Rendering of the right table                                     */
	/*-------------------------------------------------------------------*/
	
	for ( nX = 0; nX < PPU_Scr_H_u8; ++nX )
	{
		pbyChrData = PPU_BG_Base + ( *pbyNameTable << 6 ) + nYBit;
		pPalTbl = &PalTable[ ( ( ( pAttrBase[ nX >> 2 ] >> ( ( nX & 2 ) + nY4 ) ) & 3 ) << 2 ) ];
		
		pPoint[ 0 ] = pPalTbl[ pbyChrData[ 0 ] ]; 
		pPoint[ 1 ] = pPalTbl[ pbyChrData[ 1 ] ];
		pPoint[ 2 ] = pPalTbl[ pbyChrData[ 2 ] ];
		pPoint[ 3 ] = pPalTbl[ pbyChrData[ 3 ] ];
		pPoint[ 4 ] = pPalTbl[ pbyChrData[ 4 ] ];
		pPoint[ 5 ] = pPalTbl[ pbyChrData[ 5 ] ];
		pPoint[ 6 ] = pPalTbl[ pbyChrData[ 6 ] ];
		pPoint[ 7 ] = pPalTbl[ pbyChrData[ 7 ] ];
		pPoint += 8;
		
		// Callback at PPU read/write
		MapperPPU( PATTBL( pbyChrData ) );
		
		++pbyNameTable;
	}
	
	/*-------------------------------------------------------------------*/
	/*  Rendering of the block of the right end                          */
	/*-------------------------------------------------------------------*/
	
	pbyChrData = PPU_BG_Base + ( *pbyNameTable << 6 ) + nYBit;
	pPalTbl = &PalTable[ ( ( ( pAttrBase[ nX >> 2 ] >> ( ( nX & 2 ) + nY4 ) ) & 3 ) << 2 ) ];
	for ( nIdx = 0; nIdx < PPU_Scr_H_Bit; ++nIdx )
	{
		pPoint[ nIdx ] = pPalTbl[ pbyChrData[ nIdx ] ];
	}
	
	// Callback at PPU read/write
	MapperPPU( PATTBL( pbyChrData ) );
	
	/*-------------------------------------------------------------------*/
	/*  Backgroud Clipping                                               */
	/*-------------------------------------------------------------------*/
	if ( !( PPU_R1 & R1_CLIP_BG ) )
	{
		u16 *pPointTop;
		
		pPointTop = &WorkFrame[ PPU_Scanline * NES_DISP_WIDTH ];
		InfoNES_MemorySet( pPointTop, 0, 8 << 1 );
	}
	
	/*-------------------------------------------------------------------*/
	/*  Render a sprite                                                  */
	/*-------------------------------------------------------------------*/
	
	/* MMC5 VROM switch */
	MapperRenderScreen( 0 );
	
	if ( PPU_R1 & R1_SHOW_SP )
	{
		// Reset Scanline Sprite Count
		PPU_R2 &= ~R2_MAX_SP;
		
		// Reset sprite buffer
		InfoNES_MemorySet( pSprBuf, 0, sizeof pSprBuf );
		
		// Render a sprite to the sprite buffer
		nSprCnt = 0;
		for ( pSPRRAM = SPRRAM + ( 63 << 2 ); pSPRRAM >= SPRRAM; pSPRRAM -= 4 )
		{
			nY = pSPRRAM[ SPR_Y ] + 1;
			if ( nY > PPU_Scanline || nY + PPU_SP_Height <= PPU_Scanline )
				continue;  // Next sprite
			
			/*-------------------------------------------------------------------*/
			/*  A sprite in scanning line                                        */
			/*-------------------------------------------------------------------*/
			
			// Holizontal Sprite Count +1
			++nSprCnt;
			
			nAttr = pSPRRAM[ SPR_ATTR ];
			nYBit = PPU_Scanline - nY;
			nYBit = ( nAttr & SPR_ATTR_V_FLIP ) ? ( PPU_SP_Height - nYBit - 1 ) << 3 : nYBit << 3;
			
			if ( PPU_R0 & R0_SP_SIZE )
			{
				// Sprite size 8x16
				if ( pSPRRAM[ SPR_CHR ] & 1 )
				{
					pbyChrData = ChrBuf + 256 * 64 + ( ( pSPRRAM[ SPR_CHR ] & 0xfe ) << 6 ) + nYBit;
				}
				else
				{
					pbyChrData = ChrBuf + ( ( pSPRRAM[ SPR_CHR ] & 0xfe ) << 6 ) + nYBit;
				}
			}
			else
			{
				// Sprite size 8x8
				pbyChrData = PPU_SP_Base + ( pSPRRAM[ SPR_CHR ] << 6 ) + nYBit;
			}
			
			nAttr ^= SPR_ATTR_PRI;
			bySprCol = ( nAttr & ( SPR_ATTR_COLOR | SPR_ATTR_PRI ) ) << 2;
			nX = pSPRRAM[ SPR_X ];
			
			if ( nAttr & SPR_ATTR_H_FLIP )
			{
				// Horizontal flip
				if ( pbyChrData[ 7 ] )
					pSprBuf[ nX ]     = bySprCol | pbyChrData[ 7 ];
				if ( pbyChrData[ 6 ] )
					pSprBuf[ nX + 1 ] = bySprCol | pbyChrData[ 6 ];
				if ( pbyChrData[ 5 ] )
					pSprBuf[ nX + 2 ] = bySprCol | pbyChrData[ 5 ];
				if ( pbyChrData[ 4 ] )
					pSprBuf[ nX + 3 ] = bySprCol | pbyChrData[ 4 ];
				if ( pbyChrData[ 3 ] )
					pSprBuf[ nX + 4 ] = bySprCol | pbyChrData[ 3 ];
				if ( pbyChrData[ 2 ] )
					pSprBuf[ nX + 5 ] = bySprCol | pbyChrData[ 2 ];
				if ( pbyChrData[ 1 ] )
					pSprBuf[ nX + 6 ] = bySprCol | pbyChrData[ 1 ];
				if ( pbyChrData[ 0 ] )
					pSprBuf[ nX + 7 ] = bySprCol | pbyChrData[ 0 ];
			}
			else
			{
				// Non flip
				if ( pbyChrData[ 0 ] )
					pSprBuf[ nX ]     = bySprCol | pbyChrData[ 0 ];
				if ( pbyChrData[ 1 ] )
					pSprBuf[ nX + 1 ] = bySprCol | pbyChrData[ 1 ];
				if ( pbyChrData[ 2 ] )
					pSprBuf[ nX + 2 ] = bySprCol | pbyChrData[ 2 ];
				if ( pbyChrData[ 3 ] )
					pSprBuf[ nX + 3 ] = bySprCol | pbyChrData[ 3 ];
				if ( pbyChrData[ 4 ] )
					pSprBuf[ nX + 4 ] = bySprCol | pbyChrData[ 4 ];
				if ( pbyChrData[ 5 ] )
					pSprBuf[ nX + 5 ] = bySprCol | pbyChrData[ 5 ];
				if ( pbyChrData[ 6 ] )
					pSprBuf[ nX + 6 ] = bySprCol | pbyChrData[ 6 ];
				if ( pbyChrData[ 7 ] )
					pSprBuf[ nX + 7 ] = bySprCol | pbyChrData[ 7 ];
			}
		}
		
		// Rendering sprite
		pPoint -= ( NES_DISP_WIDTH - PPU_Scr_H_Bit );
		for ( nX = 0; nX < NES_DISP_WIDTH; ++nX )
		{
			nSprData = pSprBuf[ nX ];
			if ( nSprData  && ( nSprData & 0x80 || pPoint[ nX ] & 0x8000 ) )
			{
				pPoint[ nX ] = PalTable[ ( nSprData & 0xf ) + 0x10 ];
			}
		}
		
		/*-------------------------------------------------------------------*/
		/*  Sprite Clipping                                                  */
		/*-------------------------------------------------------------------*/
		if ( !( PPU_R1 & R1_CLIP_SP ) )
		{
			u16 *pPointTop;
			
			pPointTop = &WorkFrame[ PPU_Scanline * NES_DISP_WIDTH ];
			InfoNES_MemorySet( pPointTop, 0, 8 << 1 );
		}
		
		if ( nSprCnt >= 8 )
			PPU_R2 |= R2_MAX_SP;  // Set a flag of maximum sprites on scanline
  }
}

/*===================================================================*/
/*                                                                   */
/* InfoNES_GetSprHitY() : Get a position of scanline hits sprite #0  */
/*                                                                   */
/*===================================================================*/
void InfoNES_GetSprHitY()
{
/*
* Get a position of scanline hits sprite #0
*
	*/
	
	int nYBit;
	u32 *pdwChrData;
	int nOff;
	
	if ( SPRRAM[ SPR_ATTR ] & SPR_ATTR_V_FLIP )
	{
		// Vertical flip
		nYBit = ( PPU_SP_Height - 1 ) << 3;
		nOff = -2;
	}
	else
	{
		// Non flip
		nYBit = 0;
		nOff = 2;
	}
	
	if ( PPU_R0 & R0_SP_SIZE )
	{
		// Sprite size 8x16
		if ( SPRRAM[ SPR_CHR ] & 1 )
		{
			pdwChrData = (u32 *)( ChrBuf + 256 * 64 + ( ( SPRRAM[ SPR_CHR ] & 0xfe ) << 6 ) + nYBit );
		}
		else
		{
			pdwChrData = (u32 * )( ChrBuf + ( ( SPRRAM[ SPR_CHR ] & 0xfe ) << 6 ) + nYBit );
		} 
	}
	else
	{
		// Sprite size 8x8
		pdwChrData = (u32 *)( PPU_SP_Base + ( SPRRAM[ SPR_CHR ] << 6 ) + nYBit );
	}
	
	if ( ( SPRRAM[ SPR_Y ] + 1 <= SCAN_UNKNOWN_START ) && ( SPRRAM[SPR_Y] > 0 ) )
	{
		int nLine;
		for ( nLine = 0; nLine < PPU_SP_Height; nLine++ )
		{
			if ( pdwChrData[ 0 ] | pdwChrData[ 1 ] )
			{
				// Scanline hits sprite #0
				SpriteJustHit = SPRRAM[SPR_Y] + 1 + nLine;
				nLine = SCAN_VBLANK_END;
			}
			pdwChrData += nOff;
		}
	} else {
		// Scanline didn't hit sprite #0
		SpriteJustHit = SCAN_UNKNOWN_START + 1;
	}
}

/*===================================================================*/
/*                                                                   */
/*            InfoNES_SetupChr() : Develop character data            */
/*                                                                   */
/*===================================================================*/
void InfoNES_SetupChr()
{
/*
*  Develop character data
*
	*/
	
	u8 *pbyBGData;
	u8 byData1;
	u8 byData2;
	int nIdx;
	int nY;
	int nOff;
	static u8 *pbyPrevBank[ 8 ];
	int nBank;
	
	for ( nBank = 0; nBank < 8; ++nBank )
	{
		if ( pbyPrevBank[ nBank ] == PPUBANK[ nBank ] && !( ( ChrBufUpdate >> nBank ) & 1 ) )
			continue;  // Next bank
		
		/*-------------------------------------------------------------------*/
		/*  An address is different from the last time                       */
		/*    or                                                             */
		/*  An update flag is being set                                      */
		/*-------------------------------------------------------------------*/
		
		for ( nIdx = 0; nIdx < 64; ++nIdx )
		{
			nOff = ( nBank << 12 ) + ( nIdx << 6 );
			
			for ( nY = 0; nY < 8; ++nY )
			{
				pbyBGData = PPUBANK[ nBank ] + ( nIdx << 4 ) + nY;
				
				byData1 = ( ( pbyBGData[ 0 ] >> 1 ) & 0x55 ) | ( pbyBGData[ 8 ] & 0xAA );
				byData2 = ( pbyBGData[ 0 ] & 0x55 ) | ( ( pbyBGData[ 8 ] << 1 ) & 0xAA );
				
				ChrBuf[ nOff ]     = ( byData1 >> 6 ) & 3;
				ChrBuf[ nOff + 1 ] = ( byData2 >> 6 ) & 3;
				ChrBuf[ nOff + 2 ] = ( byData1 >> 4 ) & 3;
				ChrBuf[ nOff + 3 ] = ( byData2 >> 4 ) & 3;
				ChrBuf[ nOff + 4 ] = ( byData1 >> 2 ) & 3;
				ChrBuf[ nOff + 5 ] = ( byData2 >> 2 ) & 3;
				ChrBuf[ nOff + 6 ] = byData1 & 3;
				ChrBuf[ nOff + 7 ] = byData2 & 3;
				
				nOff += 8;
			}
		}
		// Keep this address
		pbyPrevBank[ nBank ] = PPUBANK[ nBank ];
	}
	
	// Reset update flag
	ChrBufUpdate = 0;
}
